package control;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

import data.PDMNode;

/**
 * PDMLink is a class that has static method to help link the timing
 * 
 * @author hongyuchen & Sarah
 * 
 */
public class PDMLink {

	/**
	 * This method initializes the nodes without any dependencies
	 * 
	 * @param pdmNodes
	 *            A hashmap containing all nodes in the list
	 */
	public static void initForward(LinkedHashMap<String, PDMNode> pdmNodes) {
		for (String key : pdmNodes.keySet()) {
			ArrayList<PDMNode> dep = pdmNodes.get(key).getDependancies();
			if (dep.size() == 0) {
				pdmNodes.get(key).setEarlyStart(0);
				pdmNodes.get(key).setEarlyEnd(
						0 + pdmNodes.get(key).getDuration());
			}
			System.out.println("nodekey: " + pdmNodes.get(key).getNodeKey()
					+ " early start: " + pdmNodes.get(key).getEarlyStart()
					+ " early end: " + pdmNodes.get(key).getEarlyEnd());
		}
	}

	/**
	 * This method links the nodes together with their correct early start and
	 * early end times
	 * 
	 * @param pdmNodes
	 *            A hashmap containing all nodes in the list
	 */
	public static void setNumbersForward(LinkedHashMap<String, PDMNode> pdmNodes) {
		for (String key : pdmNodes.keySet()) {
			ArrayList<PDMNode> d = pdmNodes.get(key).getDependancies();
			int max = -1;
			for (int i = 0; i < d.size(); ++i) {
				if (d.get(i).getEarlyEnd() > max) {
					max = d.get(i).getEarlyEnd();
				}
				pdmNodes.get(key).setEarlyStart(max);
				pdmNodes.get(key).setEarlyEnd(
						max + pdmNodes.get(key).getDuration());
			}
			System.out.println("nodekey: " + pdmNodes.get(key).getNodeKey()
					+ " early start: " + pdmNodes.get(key).getEarlyStart()
					+ " early end: " + pdmNodes.get(key).getEarlyEnd());
		}
	}

	/**
	 * This method initalizes all the nodes with no dependents
	 * 
	 * @param pdmNodes
	 *            A hashmap containing all nodes in the list
	 */
	public static void initBackward(LinkedHashMap<String, PDMNode> pdmNodes) {
		int maxLateEnd = 0;
		for (String key : pdmNodes.keySet()) {
			ArrayList<PDMNode> dep = pdmNodes.get(key).getDependents();
			if (dep.size() == 0 && pdmNodes.get(key).getEarlyEnd() > maxLateEnd)
				maxLateEnd = pdmNodes.get(key).getEarlyEnd();
		}
		for (String key : pdmNodes.keySet()) {
			ArrayList<PDMNode> dep = pdmNodes.get(key).getDependents();
			if (dep.size() == 0) {
				pdmNodes.get(key).setLateEnd(maxLateEnd);
				pdmNodes.get(key).setLateStart(
						pdmNodes.get(key).getLateEnd()
								- pdmNodes.get(key).getDuration());
			}
			System.out.println("nodekey: " + pdmNodes.get(key).getNodeKey()
					+ " early start: " + pdmNodes.get(key).getEarlyStart()
					+ " early end: " + pdmNodes.get(key).getEarlyEnd()
					+ " late start: " + pdmNodes.get(key).getLateStart()
					+ " late end: " + pdmNodes.get(key).getLateEnd());
		}
	}

	/**
	 * This method connects the numbers with the correct lateStart and lateEnd
	 * time
	 * 
	 * @param pdmNodes
	 *            A hashmap containing all nodes in the list
	 */
	public static void setNumbersBackward(
			LinkedHashMap<String, PDMNode> pdmNodes) {

		List<String> keyList = new ArrayList<String>(pdmNodes.keySet());
		int idx = keyList.size();

		ArrayList<PDMNode> nodes = new ArrayList<PDMNode>();

		for (String key : pdmNodes.keySet()) {
			nodes.add(pdmNodes.get(key));
		}

		for (int j = nodes.size() - 1; j >= 0; --j) {
			String key = nodes.get(j).getNodeKey();
			ArrayList<PDMNode> d = pdmNodes.get(key).getDependents();
			int min = 100000;
			for (int i = 0; i < d.size(); ++i) {
				if (d.get(i).getLateStart() < min) {
					min = d.get(i).getLateStart();
				}
				pdmNodes.get(key).setLateEnd(min);
				pdmNodes.get(key).setLateStart(
						min - pdmNodes.get(key).getDuration());
			}
			System.out.println("nodekey: " + pdmNodes.get(key).getNodeKey()
					+ " early start: " + pdmNodes.get(key).getEarlyStart()
					+ " early end: " + pdmNodes.get(key).getEarlyEnd()
					+ " late start: " + pdmNodes.get(key).getLateStart()
					+ " late end: " + pdmNodes.get(key).getLateEnd());
		}
	}

	/*
	 * public static void findCriticalPath(LinkedHashMap<String, PDMNode>
	 * pdmNodes) { ArrayList<ArrayList<PDMNode>> temp = new
	 * ArrayList<ArrayList<PDMNode>>();
	 * 
	 * for (String key : pdmNodes.keySet()) { ArrayList<PDMNode> list = new
	 * ArrayList<PDMNode>(); list.add(pdmNodes.get(key)); ArrayList<PDMNode> d =
	 * pdmNodes.get(key).getDependancies(); for (int i = 0; i < d.size(); ++i) {
	 * if(getFloat(d.get(i))) {
	 * 
	 * }
	 * 
	 * } }
	 * 
	 * }
	 */

	public ArrayList<ArrayList<PDMNode>> getCriticalPath(
			LinkedHashMap<String, PDMNode> pdmNodes) {
		ArrayList<PDMNode> nodes = new ArrayList<PDMNode>();
		for (String key : pdmNodes.keySet()) {
			ArrayList<PDMNode> dep = pdmNodes.get(key).getDependancies();
			if (dep.size() == 0 && hasNoFloat(pdmNodes.get(key))) {
				nodes.add(pdmNodes.get(key));
			}
		}

		ArrayList<ArrayList<ArrayList<PDMNode>>> paths = new ArrayList<ArrayList<ArrayList<PDMNode>>>();
		for (PDMNode n : nodes) {
			ArrayList<PDMNode> list = new ArrayList<PDMNode>();
			list.add(n);
			outPath.clear();
			getPath(list);
			paths.add(new ArrayList<ArrayList<PDMNode>>(outPath));
		}

		ArrayList<ArrayList<PDMNode>> maxList = new ArrayList<ArrayList<PDMNode>>();
		maxList.add(new ArrayList<PDMNode>());
		for (ArrayList<ArrayList<PDMNode>> a : paths) {
			// of all the paths found, find the duration of each path, and
			// populate maxList with the longest path(s)
			for (ArrayList<PDMNode> b : a) {
				if (hasNoFloat(b)) {
					if (getDuration(b) > getDuration(maxList.get(0))) {
						maxList.clear();
						maxList.add(b);
					} else if (getDuration(b) == getDuration(maxList.get(0))) {
						maxList.add(b);
					}
				}
			}
		}

		// output critical paths
		System.out.println("Critical Paths");
		for (ArrayList<PDMNode> nodeLists : maxList) {
			int count = 0;
			for (PDMNode n : nodeLists) {
				if (count == 0) {
					count = 1;
					System.out.print(n.getNodeKey());
				} else
					System.out.print(", " + n.getNodeKey());
			}
			System.out.println();
		}
		return maxList;
	}

	// gets the duration of a Path
	private int getDuration(ArrayList<PDMNode> list) {
		int output;
		if (list.size() > 0) {
			output = list.get(list.size() - 1).getEarlyEnd();
		} else
			output = 0;

		return output;
	}

	//
	ArrayList<ArrayList<PDMNode>> outPath = new ArrayList<ArrayList<PDMNode>>();

	private void getPath(ArrayList<PDMNode> visited) {
		ArrayList<PDMNode> nodes = visited.get(visited.size() - 1)
				.getDependents();
		for (PDMNode node : nodes) {
			if (visited.contains(node))
				continue;
			if (node.getDependents().size() == 0) {
				visited.add(node);
				outPath.add(new ArrayList<PDMNode>(visited));
				visited.remove(visited.size() - 1);
				break;
			}
		}

		for (PDMNode node : nodes) {
			if (visited.contains(node) || node.getDependents().size() == 0) {
				continue;
			}
			visited.add(node);
			getPath(visited);
			visited.remove(visited.size() - 1);
		}
	}

	// Returns true if the given node has zero float/slack.
	private boolean hasNoFloat(PDMNode temp) {
		return temp.getEarlyStart() == temp.getLateStart()
				&& temp.getEarlyEnd() == temp.getLateEnd();
	}

	// Returns true if the given Path has zero float/slack.
	private boolean hasNoFloat(ArrayList<PDMNode> temp) {
		boolean output = true;
		// temp.getEarlyStart() == temp.getLateStart() && temp.getEarlyEnd() ==
		// temp.getLateEnd();

		for (PDMNode n : temp) {
			if (n.getEarlyStart() != n.getLateStart()
					|| n.getEarlyEnd() != n.getLateEnd())
				output = false;
		}

		return output;
	}
}
